package code

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

type Opcode byte

const (
	OpConstant Opcode = iota

	OpAdd
	OpSub
	OpMul
	OpDiv
	OpPop

	OpTrue
	OpFalse

	OpNull

	OpEq
	OpNeq
	OpGt

	OpMinus
	OpBang

	OpJNE
	OpJMP

	OpGetGlobal
	OpSetGlobal
	OpGetLocal
	OpSetLocal
	OpGetFree

	OpGetBuiltin

	OpArray
	OpHash

	OpIndex

	OpCall
	OpRetVal
	OpRet

	OpClosure
	OpCurrentClosure
)

var definitions = map[Opcode]*Definition{
	OpConstant: {"OpConstant", []int{2}},

	OpAdd: {"OpAdd", []int{}},
	OpSub: {"OpSub", []int{}},
	OpMul: {"OpMul", []int{}},
	OpDiv: {"OpDiv", []int{}},
	OpPop: {"OpPop", []int{}},

	OpTrue:  {"OpTrue", []int{}},
	OpFalse: {"OpFalse", []int{}},

	OpNull: {"OpNull", []int{}},

	OpEq:  {"OpEq", []int{}},
	OpNeq: {"OpNeq", []int{}},
	OpGt:  {"OpGt", []int{}},

	OpMinus: {"OpMinus", []int{}},
	OpBang:  {"OpBang", []int{}},

	OpJNE: {"OpJNE", []int{2}},
	OpJMP: {"OpJMP", []int{2}},

	OpGetGlobal: {"OpGetGlobal", []int{2}},
	OpSetGlobal: {"OpSetGlobal", []int{2}},
	OpGetLocal:  {"OpSetGlobal", []int{1}},
	OpSetLocal:  {"OpSetGlobal", []int{1}},
	OpGetFree:   {"OpGetFree", []int{1}},

	OpArray: {"OpArray", []int{2}},
	OpHash:  {"OpHash", []int{2}},

	OpIndex: {"OpIndex", []int{}},

	OpCall:   {"OpCall", []int{1}},
	OpRetVal: {"OpRetVal", []int{}},
	OpRet:    {"OpRet", []int{}},

	OpGetBuiltin: {"OpGetBuiltin", []int{1}},

	// 分别是函数地址和自由变量数量
	OpClosure:        {"OpClosure", []int{2, 1}},
	OpCurrentClosure: {"OpCurrentClosure", []int{}},
}

type Instructions []byte

func (ins Instructions) String() string {
	var out bytes.Buffer
	i := 0
	for i < len(ins) {
		def, err := Lookup(ins[i])
		if err != nil {
			fmt.Fprintf(&out, "ERROR: %s\n", err)
			continue
		}
		operands, read := ReadOperands(def, ins[i+1:])
		fmt.Fprintf(&out, "%04d %s\n", i, ins.fmtInstruction(def, operands))
		i += 1 + read
	}
	return out.String()
}

func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
	operands := make([]int, len(def.OperandWidths))
	offset := 0

	for i, width := range def.OperandWidths {
		switch width {
		case 2:
			operands[i] = int(ReadUint16(ins[offset:]))
		case 1:
			operands[i] = int(ReadUint8(ins[offset:]))
		}
		offset += width
	}
	return operands, offset
}

func ReadUint8(ins Instructions) uint8 {
	return uint8(ins[0])
}

func ReadUint16(instructions Instructions) uint16 {
	return binary.BigEndian.Uint16(instructions)
}

func (ins Instructions) fmtInstruction(def *Definition, operands []int) string {
	operandCount := len(def.OperandWidths)
	if len(operands) != operandCount {
		return fmt.Sprintf("ERROR: operand len %d does not match defined %d\n", len(operands), operandCount)
	}
	switch operandCount {
	case 0:
		return def.Name
	case 1:
		return fmt.Sprintf("%s %d", def.Name, operands[0])
	case 2:
		return fmt.Sprintf("%s %d %d", def.Name, operands[0], operands[1])
	}
	return fmt.Sprintf("ERROR: unhandled operandCount for %s\n", def.Name)
}

type Definition struct {
	Name          string
	OperandWidths []int
}

func Lookup(op byte) (*Definition, error) {
	def, ok := definitions[Opcode(op)]
	if !ok {
		return nil, fmt.Errorf("opcode %d undefined", op)
	}
	return def, nil
}

func Make(op Opcode, operands ...int) []byte {
	// 取得指令的定义
	def, ok := definitions[op]
	if !ok {
		return []byte{}
	}
	// 计算指令总长度
	insLen := 1
	for _, w := range def.OperandWidths {
		insLen += w
	}
	// 为指令分配内存
	instruction := make([]byte, insLen)
	// 指令的第一个字节是操作码
	instruction[0] = byte(op)
	offset := 1
	for i, o := range operands {
		width := def.OperandWidths[i]
		switch width {
		case 2:
			//当前部分长度为2字节，将它们转为大端序存入指令
			binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
		case 1:
			instruction[offset] = byte(o)
		}
		offset += width
	}
	return instruction
}
